home *** CD-ROM | disk | FTP | other *** search
/ Celestin Apprentice 2 / Apprentice-Release2.iso / Source Code / Libraries / SAT 2.1.2 / OffscreenToys SAT / OffscreenToysSAT.p < prev    next >
Encoding:
Text File  |  1994-06-30  |  22.5 KB  |  732 lines  |  [TEXT/PJMM]

  1. {--------- OFFSCREEN TOYS with SAT ---------}
  2. {by Ingemar Ragnemalm 1994}
  3.  
  4. {Offscreen Toys is a nice little demo I made to make a SAT-like demo with complete source}
  5. {code, independent of any libraries (except Apple's). Now, this should be easier to do with}
  6. {SAT, right? Well, partially so, but while adapting it to SAT, I ran into a minor flaw that}
  7. {SAT had (and doesn't have from version 2.1 and up), namely that drawing took place after}
  8. {moving sprites, but before checking for collisions, which didn't look too good in programs}
  9. {where sprites bounce off each other. After fixing this flaw, I would say that the SAT}
  10. {version is indeed a bit better than the independent version. The result is quite a bit faster}
  11. {and with asynch sound.}
  12. {}
  13. {As yet another SAT demo, what does it give us?}
  14. {• If you wonder what it costs to have a real event loop, or good collision handling, this demo}
  15. {shows that pretty well.}
  16. {• Demonstrates a moveable window AND fast mode at the same time, with the precautions}
  17. {that demands when moving the window. (The window mustn't be moved outside the screen,}
  18. {some internal SAT variables – that you otherwise should never care about – must be adjusted,}
  19. {and we must stay word-aligned to make it work in b/w).}
  20. {}
  21. {I don't consider this demo final in any way. Known flaws:}
  22. {• I have made some mistakes in the port-setting. Nothing fatal, I think. FIXED.}
  23. {• I don't protect the user from moving the window outside the screen, which might be fatal}
  24. {when the SAT blitters are turned on. FIXED.}
  25. {• Like in the "real" Offscreen Toys, there is a bug that causes sprites to disappear for a short}
  26. {while, since the position gets negative. No big deal.}
  27. {• The code for the marble should be separated from the main program, to make the code easier}
  28. {to follow.}
  29. {I'll fix those things when I find time and inspiration for it - but you are welcome to do it if}
  30. {you want!}
  31.  
  32. program OffscreenToysSAT;
  33.     uses
  34.         SAT;
  35.  
  36. { --- PART 1: Variables and constants: -----------------------------------------}
  37.  
  38.     const
  39.         kAppleID = 128;
  40.         kFileID = 129;
  41.         kMBarHeight = 20;    { We assume 20 pixels menu bar for window sizing and dragging.}
  42.  
  43.         kWindID = 128;            { Window resource ID }
  44.         kAboutAlertID = 128;    { Alert resource ID }
  45.  
  46.         kSpriteNumber = 5;        { Number of moving objects }
  47.     var
  48.         gColorQDFlag: Boolean;        {True if 32-bit QD exists. If not, we run everything in b/w.}
  49.         gHasWNE: Boolean;        {True if we can use WaitNextEvent}
  50.         gSoundFlag: Boolean;        {True if we want sound.}
  51.         gFast: Boolean;
  52.  
  53.         gWhoa: Boolean;            {True when we want to quit}
  54.         gCollisionFlag: Boolean;    {Collisions or not?}
  55.  
  56. {Menu handles}
  57.         appleMenu, fileMenu: MenuHandle;
  58.  
  59. {The window we'll be using}
  60.         gWind: WindowPtr;
  61.  
  62. {Our two offscreens:}
  63. {offScreen, backScreen: GrafPtr;}
  64.  
  65. {A cicn loded as a face}
  66.         gCicn: FacePtr;
  67.         kgck: Handle;
  68.  
  69. {Sprite information. In real games, I prefer making a linked list of records, like I do in}
  70. {SAT, and a lot more information for each, but here we want it *simple*.}
  71. {- position: The positions in local coordinates for the window}
  72. {- fixedPos: 16 times position, which gives us fixed-point numbers}
  73. {- speed: Speed vectors that is added to fixedPos for every frame}
  74. {- r: Rectangles used in drawing, for remembering what part of the screen to update}
  75. {position, fixedPos: array[1..kSpriteNumber] of Point;}
  76. {speed: array[1..kSpriteNumber] of Point;}
  77. {r: array[1..kSpriteNumber] of Rect;}
  78.  
  79. {A longint that shows how big the "bowl" is (assumes circular, not oval, bowl!):}
  80.  
  81.         gBowlSize: Longint;
  82.  
  83. { --- PART 2: Various general, reuseable routines, mostly glue: ---------------------}
  84.  
  85. {BailOut: Emergency exit. We go here on most errors. Real programs report what the}
  86. {problem is. You may wish to put a breakpoint in BailOut when debugging.}
  87.  
  88.     procedure BailOut;
  89.     begin
  90.         SysBeep(1); {Minimal error message. Use alert in real programs.}
  91.         halt;
  92.     end;
  93.  
  94. {Several functions deleted since SAT handles what they do.}
  95.  
  96. { --- PART 3: Application specific routines: ---------------------------------}
  97.  
  98. {mouse clicks, keydowns, background tasks and update events: This is where all}
  99. {the action is. :-) I include some empty procedures for you to fill in if you want to}
  100. {use this demo as application shell.}
  101.  
  102. {Mouse click in window content}
  103.  
  104.     procedure DoMouse (where: Point; modifiers: Longint);
  105.     begin
  106.     end;
  107.  
  108. {Keydown.}
  109.  
  110.     procedure DoKey (theKey: Char; modifiers: Longint);
  111.     begin
  112.     end;
  113.  
  114.     const
  115.         kWallBounce = 7;                            {1/10-ths of speed kept after wallbounce}
  116.         kBallDiameterSquared = 32 * 32;            {Diameter 32, squared}
  117.  
  118.  
  119. {A rather boring subroutine that moves the sprites i and j away from each other.}
  120. {I almost didn't want to put this in, making the demo unnecessarily big, but I wanted}
  121. {decent collisions. If anyone can suggest a better (simpler) collision handling for circular}
  122. {objects, I'd be happy to put it in.}
  123. {I use a line drawing algorithm for it. I'm sure there are better ways. This is of questionable}
  124. {value as reuseable code - depends on your application.}
  125.     procedure Separate (i, j: SpritePtr);
  126.         var
  127.             initVector, nowVector: Point;
  128.             absH, absV: integer;
  129.             moveH, moveV: integer;
  130.             frac: integer;
  131. {Normal signum function (which I don't think is in the libs)}
  132.         function Sgn (x: integer): integer;
  133.         begin
  134.             if x > 0 then
  135.                 Sgn := 1
  136.             else if x < 0 then
  137.                 Sgn := -1
  138.             else
  139.                 Sgn := 0;
  140.         end;
  141.  
  142.     begin {Separate}
  143.         initVector.h := i^.position.h - j^.position.h;
  144.         initVector.v := i^.position.v - j^.position.v;
  145.         absH := abs(initVector.h);
  146.         absV := abs(initVector.v);
  147.         moveH := Sgn(initVector.h);
  148.         moveV := Sgn(initVector.v);
  149.         if moveH = 0 then
  150.             if moveV = 0 then
  151.                 moveV := 1;
  152.         repeat
  153.             if absH > absV then
  154.                 begin
  155.                     i^.position.h := i^.position.h + moveH;
  156.                     j^.position.h := j^.position.h - moveH;
  157.                     frac := frac + absV;
  158.                     if frac > absH then
  159.                         begin
  160.                             i^.position.v := i^.position.v + moveV;
  161.                             j^.position.v := j^.position.v - moveV;
  162.                             frac := frac - absH;
  163.                         end
  164.                 end
  165.             else
  166.                 begin
  167.                     i^.position.v := i^.position.v + moveV;
  168.                     j^.position.v := j^.position.v - moveV;
  169.                     frac := frac + absH;
  170.                     if frac > absV then
  171.                         begin
  172.                             i^.position.h := i^.position.h + moveH;
  173.                             j^.position.h := j^.position.h - moveH;
  174.                             frac := frac - absV;
  175.                         end
  176.                 end;
  177.             nowVector.h := i^.position.h - j^.position.h;
  178.             nowVector.v := i^.position.v - j^.position.v;
  179.         until longint(nowVector.h) * nowVector.h + longint(nowVector.v) * nowVector.v > kBallDiameterSquared;
  180.         i^.fixedPos.h := BSL(i^.position.h, 4); {Make fixedPos by shifting in the 4 binary "decimals"}
  181.         i^.fixedPos.v := BSL(i^.position.v, 4);
  182. {Behövs inte j också???}
  183.         j^.fixedPos.h := BSL(j^.position.h, 4); {Make fixedPos by shifting in the 4 binary "decimals"}
  184.         j^.fixedPos.v := BSL(j^.position.v, 4);
  185.  
  186.     end;{Separate}
  187.  
  188. {Split a vector (v1) into one component parallell to another vector (direction) and one}
  189. {orthogonal to it.}
  190.     procedure SplitVector (v1, direction: Point; var parallell, normal: Point);
  191.         var
  192.             l2, v1pr: Longint;
  193.     begin
  194. {parallell := direction * (v1 DOT direction) /|direction|**2}
  195. {normal := v1 - parallell}
  196.  
  197.         l2 := direction.h * direction.h + direction.v * direction.v; {Squared length of "direction"}
  198.         v1pr := v1.h * direction.h + v1.v * direction.v; {Scalar product}
  199.  
  200.         parallell.h := direction.h * v1pr div l2;
  201.         parallell.v := direction.v * v1pr div l2;
  202.         normal.h := v1.h - parallell.h;
  203.         normal.v := v1.v - parallell.v;
  204.     end; {SplitVector}
  205.  
  206.     procedure HandleMarble (i: SpritePtr);
  207.         var
  208.             vector: Point;
  209.     begin
  210.         i^.fixedPos.h := i^.fixedPos.h + i^.speed.h; {Modify fixed-point position by speed}
  211.         i^.fixedPos.v := i^.fixedPos.v + i^.speed.v;
  212.         i^.position.h := BSR(i^.fixedPos.h, 4); {Make position by shifting away the 4 binary "decimals"}
  213.         i^.position.v := BSR(i^.fixedPos.v, 4);
  214.         if i^.fixedPos.h + i^.speed.h < 0 then
  215.             i^.speed.h := abs(i^.speed.h) * kWallBounce div 10 + 1;
  216.         if i^.fixedPos.v + i^.speed.v < 0 then
  217.             i^.speed.v := abs(i^.speed.v) * kWallBounce div 10 + 1;
  218.         if i^.position.h + gCicn^.iconMask.bounds.right > gSAT.offScreen^.portRect.right then
  219.             i^.speed.h := -abs(i^.speed.h) * kWallBounce div 10 - 1;
  220.         if i^.position.v + gCicn^.iconMask.bounds.bottom > gSAT.offScreen^.portRect.bottom then
  221.             i^.speed.v := -abs(i^.speed.v) * kWallBounce div 10 - 1;
  222.  
  223. {Are we in the bowl? If we are, accelerate towards the center.}
  224.         vector.h := i^.position.h + 16 - BSR(gSAT.offScreen^.portRect.right, 1);
  225.         vector.v := i^.position.v + 16 - BSR(gSAT.offScreen^.portRect.bottom, 1);
  226.         if (vector.h * vector.h + vector.v * vector.v) < gBowlSize then
  227.             begin
  228.                 i^.speed.h := i^.speed.h - vector.h div 2;
  229.                 i^.speed.v := i^.speed.v - vector.v div 2;
  230.             end;
  231.     end; {position/speed loop}
  232.  
  233.     procedure HitMarble (i, j: SpritePtr);
  234.         var
  235.             vector: Point;
  236.             squaredLength: Longint;
  237.             p1, p2, n1, n2, tmpSpeed: Point;
  238.     begin
  239.         if not gCollisionFlag then
  240.             exit(HitMarble);
  241.  
  242. {Find the vector between them}
  243.         vector.h := i^.position.h - j^.position.h;
  244.         vector.v := i^.position.v - j^.position.v;
  245.         squaredLength := longint(vector.h) * vector.h + longint(vector.v) * vector.v;
  246. {If it is shorter than the diameter of a ball…}
  247.         if squaredLength < kBallDiameterSquared then
  248.             begin
  249. {Move them away from each other}
  250.                 Separate(i, j);
  251.  
  252. {if false then}
  253.                 begin
  254. {Swap the speed components that are parallell to "vector" (this allows for "touches", very}
  255. {nice and realistic bounces)}
  256.                     SplitVector(i^.speed, vector, p1, n1);
  257.                     SplitVector(j^.speed, vector, p2, n2);
  258.  
  259.                     j^.speed.h := p1.h + n2.h;
  260.                     j^.speed.v := p1.v + n2.v;
  261.  
  262.                     i^.speed.h := p2.h + n1.h;
  263.                     i^.speed.v := p2.v + n1.v;
  264.                 end;
  265.  
  266. {Old Offscreen Toys just swapped the speed, as commented out below. This is not as realistic.}
  267. {tmpSpeed := i^.speed;}
  268. {i^.speed := j^.speed;}
  269. {j^.speed := tmpSpeed;}
  270.  
  271. {Play a sound. SAT is good at playing sounds!}
  272.                 if gSoundFlag then
  273.                     SATSoundPlay(kgck, 1, false);
  274.  
  275.             end;
  276.     end; {collision loop}
  277.  
  278.     procedure SetupMarble (i: SpritePtr);
  279.     begin
  280.         i^.fixedPos.h := BSL(i^.position.h, 4);
  281.         i^.fixedPos.v := BSL(i^.position.v, 4);
  282.         i^.speed.h := Random mod 32;
  283.         i^.speed.v := Random mod 32;
  284.         i^.task := @HandleMarble;
  285.         i^.hitTask := @HitMarble;
  286.         i^.face := gCicn;
  287.         SetRect(i^.hotRect, 0, 0, 32, 32);
  288.     end;
  289.  
  290. {A static variable only used in DrawBackground}
  291.     var
  292.         thePat: PixPatHandle;
  293.  
  294.     procedure DrawBackground;
  295.         const
  296.             patID = 128;
  297.         var
  298.             r: Rect;
  299.             mycolorFlag: Boolean;
  300.  
  301. {A little routine for setting the forecolor with a single line.}
  302.         procedure OTForeColor (red, green, blue: integer);
  303.             var
  304.                 theColor: RGBColor;
  305.         begin
  306.             theColor.red := red;
  307.             theColor.green := green;
  308.             theColor.blue := blue;
  309.             RGBForeColor(theColor);
  310.         end;
  311.  
  312.         var
  313.             savePort: GrafPtr;
  314.             saveGD: GDHandle;
  315.     begin {DrawBackground}
  316.         SATGetPort(savePort, saveGD);
  317.  
  318.         SATSetPortBackScreen;
  319.  
  320. {Do some drawing in backScreen. First, we paint a pattern (using a 'ppat' resource):}
  321.  
  322. {For drawing the background, let's make a local flag that tells us if we shold draw}
  323. {b/w patterns or color ones.}
  324.         if gColorQDFlag then
  325.             mycolorFlag := gSAT.initDepth > 1
  326.         else
  327.             mycolorFlag := false;
  328.  
  329.         if mycolorFlag then
  330.             begin
  331.                 if thePat = nil then
  332.                     thePat := GetPixPat(patID);
  333.                 PenPixPat(thePat)
  334.             end
  335.         else
  336.             begin
  337.                 if thePat = nil then
  338.                     thePat := PixPatHandle(GetResource('ppat', patID));
  339.                 PenPat(thePat^^.pat1Data);
  340.             end;
  341.         PaintRect(gSAT.backScreen^.portRect);
  342.         PenNormal;
  343.  
  344. {Then we draw some circles.}
  345.  
  346.         r := gSAT.backScreen^.portRect;
  347.         InsetRect(r, (r.right - r.left) div 8, (r.bottom - r.top) div 8);
  348.         gBowlSize := longint(r.right - r.left) * (r.right - r.left) div 4;        {Tells how big the "bowl" is!}
  349.         if mycolorFlag then
  350.             begin
  351.                 OTForeColor(-10000, -10000, -10000);
  352.                 PaintOval(r);
  353.             end
  354.         else
  355.             FillOval(r, ltGray);
  356.  
  357.         InsetRect(r, (r.right - r.left) div 8, (r.bottom - r.top) div 8);
  358.         if mycolorFlag then
  359.             begin
  360.                 OTForeColor(-25000, -25000, -25000);
  361.                 PaintOval(r);
  362.             end
  363.         else
  364.             FillOval(r, gray);
  365.  
  366.         InsetRect(r, (r.right - r.left) div 6, (r.bottom - r.top) div 6);
  367.         if mycolorFlag then
  368.             begin
  369.                 OTForeColor(20000, 20000, 20000);
  370.                 PaintOval(r);
  371.             end
  372.         else
  373.             FillOval(r, dkGray);
  374.  
  375.         InsetRect(r, (r.right - r.left) div 5, (r.bottom - r.top) div 5);
  376.         if mycolorFlag then
  377.             begin
  378.                 OTForeColor(0, 0, 0);
  379.                 PaintOval(r);
  380.             end
  381.         else
  382.             FillOval(r, black);
  383.  
  384.         SATSetPort(savePort, saveGD);
  385.     end; {DrawBackground}
  386.  
  387. {DoBackground: repeating tasks - this is called repeatedly, after every event we get.}
  388.  
  389. {Note: If you are making a really Mac-friendly program, this is where you should drive}
  390. {the animation. However, it is hard to get high framerate then, since other programs}
  391. {(the Finder included) will process events which will make it less smooth.}
  392.  
  393.     procedure DoBackground;
  394.         var
  395.             tmpRect: Rect;
  396.             i, j: integer;
  397.             vector: Point;
  398.             saveGD: GDHandle;
  399.             savePort: GrafPtr;
  400.             tmpSpeed: Point;
  401.     begin {DoBackground}
  402.  
  403.         RunSAT(gFast); {Eller konfigurerbart?}
  404.  
  405.     end; {DoBackground}
  406.  
  407.  
  408. {DoUpdate: handle update events, in this case by copying offScreen to the screen (gWind).}
  409.  
  410. {Note to beginners: A program without update events processing is not a real Mac program!}
  411. {All drawing you do must reach the update event handler in some way, or it might be lost,}
  412. {or worse, partially erased, which is really ugly.}
  413.  
  414.     procedure DoUpdate;
  415.         var
  416.             saveGD: GDHandle;
  417.             savePort: GrafPtr;
  418.     begin
  419.         if SATDepthChangeTest then
  420.             DrawBackground;
  421.         BeginUpdate(gWind);
  422.         PeekOffScreen;
  423.         EndUpdate(gWind);
  424.     end;
  425.  
  426. {DoAppleMenu and DoFileMenu: handle menu selections}
  427.  
  428.     procedure DoAppleMenu (item: integer);
  429.         var
  430.             str: Str255;
  431.             h: Handle;
  432.             saveGD: GDHandle;
  433.             savePort: GrafPtr;
  434.             ignore: integer;
  435.     begin
  436.         if item = 1 then
  437.             begin
  438.                 if Alert(kAboutAlertID, nil) = 1 then
  439.                     ; {Ignore result}
  440.             end
  441.         else
  442. {Apple menu other than "About": Code from TransSkel}
  443.             begin
  444.                 SATGetPort(savePort, saveGD); {I guess GetPort would be ok}
  445.                 GetItem(appleMenu, item, str);
  446.                 SetResLoad(false);
  447.                 h := GetNamedResource('DRVR', str);
  448.                 SetResLoad(true);
  449.                 if h <> nil then
  450.                     begin
  451.                         ResrvMem(SizeResource(h) + $1000);
  452.                         ignore := OpenDeskAcc(str);
  453.                     end;
  454.                 SATSetPort(savePort, saveGD);
  455.             end;
  456.     end; {DoAppleMenu}
  457.  
  458.     procedure DoFileMenu (item: integer);
  459.     begin
  460.         case item of
  461.             1:
  462. {Run animation without event processing until the user clicks the mouse}
  463. {Note: This runs the animation at maximum speed. In real programs, we}
  464. {must limit the speed with the system clock, e.g. inspect TickCount.}
  465.                 while not Button do
  466.                     DoBackground;
  467.             2: 
  468.                 begin
  469.                     gCollisionFlag := not gCollisionFlag;
  470.                     CheckItem(fileMenu, 2, gCollisionFlag);
  471.                 end;
  472.             3: 
  473.                 begin
  474.                     gFast := not gFast;
  475.                     CheckItem(fileMenu, 3, gFast);
  476.                 end;
  477.             4: 
  478.                 begin
  479.                     gSoundFlag := not gSoundFlag;
  480.                     CheckItem(fileMenu, 4, gSoundFlag);
  481.                 end;
  482. {Set the flag that tells the program to quit.}
  483.             6: 
  484.                 gWhoa := true;
  485.         end; {case}
  486.     end; {DoFileMenu}
  487.  
  488. { --- PART 4: Event processing: -----------------------------------------}
  489.  
  490. {MenuSelection: Menu selection by mouse or command-key:}
  491.  
  492.     procedure MenuSelection (whatSelection: longInt);
  493.     begin
  494.         case HiWord(whatSelection) of
  495.             kAppleID: 
  496.                 DoAppleMenu(LoWord(whatSelection));
  497.             kFileID: 
  498.                 DoFileMenu(LoWord(whatSelection));
  499.         end; {case}
  500.         HiLiteMenu(0);
  501.     end;
  502.  
  503. {MainLoop: get and process events. This is the boring standard part of all programs. I prefer}
  504. {using TransSkel to get rid of it. I don't here since I want this code to be stand-alone.}
  505.  
  506.     procedure MainLoop;
  507.         const
  508.             kSleep = 5; {Real programs may modify the sleep time depending on whether or not they are in the front}
  509.         var
  510.             hasEvent: Boolean;
  511.             theEvent: EventRecord;
  512.             theKey: Char;
  513.             whatSelection: Longint;
  514.             whichPart: integer;
  515.             whichWindow: WindowPtr;
  516.             r: rect;
  517.             p: Point;
  518.     begin
  519. {Get the next event. Use WaitNextEvent if possible.}
  520.         if gHasWNE then
  521.             hasEvent := WaitNextEvent(everyEvent, theEvent, kSleep, nil)
  522.         else
  523.             begin
  524.                 SystemTask;
  525.                 hasEvent := GetNextEvent(everyEvent, theEvent);
  526.             end;
  527.  
  528. {OK, so what happened then?}
  529.         if hasEvent then
  530.             case theEvent.what of
  531.                 mouseDown: 
  532.                     begin
  533.                         whichPart := FindWindow(theEvent.where, whichWindow);
  534.                         case whichPart of
  535.                             inMenuBar: 
  536.                                 begin
  537.                                     whatSelection := MenuSelect(theEvent.where);
  538.                                     MenuSelection(whatSelection);
  539.                                 end;
  540.                             inSysWindow: 
  541.                                 SystemClick(theEvent, whichWindow);
  542.                             inGoAway: 
  543.                                 if (TrackGoAway(whichWindow, theEvent.where)) then
  544.                                     gWhoa := true;
  545.                             inDrag: 
  546.                                 begin
  547.                                     if (whichWindow <> FrontWindow) and (BitAnd(theEvent.modifiers, cmdKey) = 0) then
  548.                                         SelectWindow(whichWindow);
  549.                                     r := gSAT.bounds;            {How big is the screen?}
  550. {Was: screenBits.bounds;{How big is the screen? (Note: Don't use screenBits for other things: it isn't a valid BitMap any more!)}
  551.                                     r.top := r.top + kMBarHeight;        { Skip down past menu bar    }
  552.  
  553.                                     InsetRect(r, 4, 4);
  554.  
  555. { LIMIT THE DRAGGING so no part of the window can get outside the screen! Cut down on r}
  556. {depending on where the click is? This is necessary if we use "fast mode", that is if we do}
  557. {RunSAT(true) or any other operation with custom blitters.}
  558.                                     SetPort(whichWindow);
  559.                                     p := theEvent.where;
  560.                                     GlobalToLocal(p);
  561.                                     r.bottom := r.bottom - (whichWindow^.portRect.bottom - p.v);
  562.                                     r.left := r.left + p.h;
  563.                                     r.right := r.right - (whichWindow^.portRect.right - p.h);
  564.  
  565.                                     DragWindow(whichWindow, theEvent.where, r);
  566.  
  567.                                     if whichWindow = gSAT.wind then
  568.                                         begin
  569. { *** Adjust gSAT.ox and gSAT.oy. *** }
  570. {gSAT.ox and gSAT.oy are internal SAT variables that tell the offset from the upper left}
  571. {corner of the animation area to the upper left corner of the screen. You normally have no}
  572. {need to use them at all.}
  573.                                             p := gSAT.wind^.portRect.topLeft;
  574.                                             LocalToGlobal(p);
  575.  
  576. {Align the offscreens to the screen AT LEAST byte-wise; for best performance, you should}
  577. {longword-align!}
  578.                                             if gSAT.initDepth = 1 then
  579.                                                 if BitAnd(p.h, 15) <> 0 then
  580.                                                     begin
  581.                                                         p.h := p.h - BitAnd(p.h, 15);
  582.                                                         MoveWindow(gSAT.wind, p.h, p.v, true)
  583.                                                     end
  584.                                                 else if gSAT.initDepth = 4 then
  585.                                                     if BitAnd(p.h, 1) <> 0 then
  586.                                                         begin
  587.                                                             p.h := p.h - 1;
  588.                                                             MoveWindow(gSAT.wind, p.h, p.v, true)
  589.                                                         end;
  590.  
  591. {Now we know where the window ends up, and can adjust gSAT.h and gSAT.v}
  592.                                             gSAT.ox := -p.h;
  593.                                             gSAT.oy := -p.v;
  594.                                         end;
  595.  
  596.                                 end;
  597.                             inGrow: 
  598.                                 ;  {Ignored - we don't resize}
  599.                             inContent: 
  600.                                 if (whichWindow <> FrontWindow) then
  601.                                     SelectWindow(whichWindow)
  602.                                 else
  603.                                     DoMouse(theEvent.where, theEvent.modifiers); {Go to application-specific mouse down handling}
  604.                         end; {case whichPart}
  605.                     end; {mouseDown}
  606.                 keyDown, autoKey: 
  607.                     begin
  608.                         theKey := char(BitAnd(theEvent.message, charCodeMask));
  609.                         if (BitAnd(theEvent.modifiers, cmdKey) <> 0) then
  610.                             MenuSelection(MenuKey(theKey))
  611.                         else
  612.                             DoKey(theKey, theEvent.modifiers);
  613.                     end;
  614.                 updateEvt:
  615. {There's only one window to bother with here, but let's make sure that's the one the Mac wants to update.}
  616.                     if WindowPtr(theEvent.message) = gWind then
  617.                         DoUpdate;
  618. {Handle disk inserts like TransSkel.}
  619.                 diskEvt: 
  620.                     if (HiWord(theEvent.message) <> noErr) then
  621.                         begin
  622.                             DILoad;
  623.                             if DIBadMount(Point($00400040), theEvent.message) = 0 then
  624.                                 ;
  625.                             DIUnload;
  626.                         end; {diskEvt}
  627.                 otherwise {Other events are ignored}
  628.             end; {case}
  629.  
  630.         DoBackground;
  631.     end;
  632.  
  633. { --- PART 5: Initializations: -----------------------------------------}
  634.  
  635. {OTInit: Initialize global flags, menus and window}
  636.  
  637.     procedure OTInit;
  638.         const
  639. {Trap numbers}
  640.             _WaitNextEvent = $A860;
  641.             _GetCIcon = $AA1E; {E.g. any Color QuickDraw routine}
  642.             k32bQD = $AB1D;
  643.             _SndPlay = $A805;
  644.     begin
  645.         gHasWNE := TrapAvailable(_WaitNextEvent);
  646.         gColorQDFlag := TrapAvailable(k32bQD) and TrapAvailable(_GetCIcon); {???}
  647.         gWhoa := false;
  648.         gCollisionFlag := false;
  649.  
  650. {gSoundFlag := TrapAvailable(_SndPlay); – Let SAT decide if we CAN or not!}
  651.         gSoundFlag := true;
  652.  
  653. {What more should I check for? Check with Gestalt instead?}
  654.  
  655.         randSeed := TickCount;            {Seed the random number generator - TickCount is good enough.}
  656.  
  657. {Get the window, a color window if we are going to use color.}
  658.         if gColorQDFlag then
  659.             gWind := GetNewCWindow(kWindId, nil, WindowPtr(-1))
  660.         else
  661.             gWind := GetNewWindow(kWindId, nil, WindowPtr(-1));
  662.  
  663. {Some menus. We could read these from resources.}
  664.         appleMenu := NewMenu(kAppleID, stringof(char($14)));
  665.         AppendMenu(appleMenu, 'About Offscreen Toys SAT…;(-');
  666.         AddResMenu(appleMenu, 'DRVR');
  667.         InsertMenu(appleMenu, 0);            { put apple menu at end of menu bar }
  668.         fileMenu := NewMenu(kFileID, 'File');
  669.         AppendMenu(fileMenu, 'Try max speed;Collisions;Use SAT blitters;Use sound;(-;Quit/Q');
  670.         InsertMenu(fileMenu, 0);            { put file menu at end of menu bar }
  671.         DrawMenuBar;
  672.         CheckItem(fileMenu, 4, gSoundFlag);
  673.     end;
  674.  
  675. {OTOffscreensInit: Initialize offscreen grafports (worlds) and draw in them.}
  676.  
  677.     procedure OTOffscreensInit;
  678.         var
  679.             saveGD: GDHandle;
  680.             savePort: GrafPtr;
  681.             r: Rect;
  682.             i: integer;
  683.             sp: SpritePtr;
  684.     begin {OTOffscreensInit}
  685.         SATGetPort(savePort, saveGD);
  686.  
  687.         ConfigureSAT(false, kNoSort, kForwardOneCollision, 32); {Only call *one* hitTask, not both.}
  688.         CustomInitSAT(0, 0, gWind^.portRect, gWind, nil, false, false, false, true, false);
  689.  
  690.         DrawBackground;
  691.  
  692. {Done drawing!}
  693. {For your own hacks, consider using a PICT resource and use GetPicture and DrawPicture to draw the}
  694. {background. Note that you'll need both a color and a b/w picture if you want it to look good in b/w.}
  695.  
  696.         SATSetPortOffScreen;
  697.         CopyBits(gSAT.backScreen^.portBits, gSAT.offScreen^.portBits, gSAT.backScreen^.portRect, gSAT.backScreen^.portRect, srcCopy, nil);
  698.  
  699.         SATSetPort(savePort, saveGD);
  700.  
  701. {Get the cicn resource}
  702. {Note: You can, of course, use several cicns and switch between.}
  703.         gCicn := GetFace(128);
  704.         kgck := SATGetNamedSound('Kgck');
  705.  
  706. {Initialize the sprites:}
  707.  
  708.         for i := 1 to kSpriteNumber do
  709.             sp := NewSprite(1, Rand(gSAT.offScreen^.portRect.right - 32), (i - 1) * (gSAT.offScreen^.portRect.bottom - 32) div 5 + Rand((gSAT.offScreen^.portRect.bottom - 32) div 5), @SetupMarble);
  710.  
  711.         if SATSoundInitChannels(2) < 2 then
  712.             SysBeep(1);
  713.     end;
  714.  
  715. { --- MAIN PROGRAM BODY: -----------------------------------------}
  716.  
  717. begin
  718.     OTInit;                    {General initializations}
  719.     OTOffscreensInit;        {Set up the offscreen grafports}
  720.     InitCursor;                {Set the cursor to arrow in case it isn't.}
  721.     SATSetPortScreen;    {The front window is a good port to use.}
  722.  
  723. {Run until quit or click in the close box.}
  724.     repeat
  725.         SATSetPortScreen;    {The front window is a good port to use.}
  726.         MainLoop;
  727.     until gWhoa;
  728.  
  729. {No cleanup is necessary here.}
  730. {We could DisposeGWorld, but that isn't necessary when we are quitting.}
  731.     SATSoundShutup;
  732. end.